<?php
namespace Pinenut\Context;

use Users\Profile;
/**
 * Объект, реализующий интерфейс доступа к данным пользователя, сделавшего запрос
 * Работает в контексте запроса, использует сессию
 * Через него происходит аутентификация (логин) и авторизация (определение привилегий)
 * 
 * 
 * @author Kosh
 *
 */

class Auth {
	
	
	/**
	 * список привилегий, которыми обладает пользователь
	 * и ключ и значение - привилегии для быстрого поиска
	 * @var array()
	 */
	protected $privileges = null;
	
	/**
	 * Профиль пользвоателя
	 * @var \Users\Profile
	 */
	protected $profile = null;
	
	/**
	 * Список
	 * @var unknown_type
	 */
	protected $asserts = array();
	
	/**
	 * ничего не делаем, всё надо делать лениво, 
	 * вдруг страничка формируется вообще без оглядки на пользователя 
	 * и его права
	 */
	function __construct() {
	}
	
	
	/**
	 * Проверка прав доступа
	 * 
	 * Привилегии должны быть нормализованы
	 * 
	 * @param array $privileges
	 * @return bool
	 */
	function checkAccess($privileges, $returnChecks = false) {
		$checks = [];
		// инициируем список привилегий
		$this->preparePrivileges();
		// для каждой привилегии проверяем её наличие в списке пользователя, 
		// если находим и утверждения верны - говорим что дуступ есть
		foreach ($privileges as $key=>$value) {
			$check = false;
			
			$privilege = $value['privilege']?$value['privilege']:null;
			$asserts = (array)$value['asserts'];
			// если только значение (ключ- индекс), то просто проверяем наличие
			// сложная привилегия с утверждением(-ями)
			if (isset($this->privileges[$privilege])) {
				$result = true;
				foreach ($asserts as $assert=>$params) {
					// если утвеждение ложно - привилегия ложна
					if (!$this->checkAssert($assert, $params)) {
						$result = false;
						break;	
					}
				}
				// если все утверждения верны - привилегия подходит, доступ разрешен
				$check = $result;
				if (!$returnChecks && $result) {
					return true;
				}
			}
			$checks[$key] = $check;
		}
		// ничего не нашли - доступ запрещен
		if (!$returnChecks) {
			return false;
		} else {
			return $checks;
		}
	}
	
	/**
	 * Готовим привилегии пользователя, если необходимо
	 * 
	 * 1) Проверяем сессию - если там пусто - считаем, 
	 * 		что это аноним и подгружаем привилегии для анонима
	 * 		Это делается для того, чтобы не хранить в сессии лишнего
	 * 2) Если - пользователь аутентифицирован - у него должны быть 
	 * 		установлены при этом роли, и время жизни, до очередной проверки ролей
	 * 
	 */
	function preparePrivileges() {
		// привилегии уже инициированы?
		if (null !== $this->privileges)
			return;	
	
		// устанавливаем привилегии для ауторизованного или не ауторизованного пользователя
		if ($this->isAuthorized()) {
			$this->privileges = \PrivilegesConfig::getPrivilegesByProperty('authorized');
				
			// получаем роли пользователя и достаём соответствеющие привилегии
			$profile = $this->profile();

			$this->privileges = array_merge($this->privileges, 
					\PrivilegesConfig::getPrivilegesOfRoles($profile->roles));

			// подменяем звездочку на идентификатор пользователя
			$this->implementIdentityToPrivileges();
		} else {
			$this->privileges = \PrivilegesConfig::getPrivilegesByProperty('unauthorized');				
		}
			
		\Log::warn('Privileges', $this->privileges);
	}
	
	/**
	 * Внедряем в привилегии идентификатор пользователя для возможности 
	 * декларировать персональные привилегии
	 */
	function implementIdentityToPrivileges() {
		$privileges = [];
		$identity = $this->identity();
		foreach ($this->privileges as $key=>$val) {
			if (is_int($key)) {
				$privileges[] = str_replace('*', $identity, $val);
			} else {
				$privileges[str_replace('*', $identity, $key)] = $val;
			}
		}
		$this->privileges = $privileges;
	}
	
	function isAuthorized() {
		return  \Session::getValue('identity') !== null;
	}

	/**
	 * Возвращаем идентификатор пользователя
	 * 
	 * @return Ambigous <NULL, unknown>
	 */
	function identity() {
		return \Session::getValue('identity');
	}
	
	
	/**
	 * Возвращаем профиль пользваотеля 
	 * 
	 * @return boolean|Ambigous <\Pinenut\Model2, \Pinenut\Model2\Finder>
	 */
	function profile() {
		$identity = $this->identity();
		if (!$identity) {
			return false;
		}
		if (!$this->profile) {
			$this->profile = Profile::find($identity);
		}		
		
		// выйти если профиль не найден, а иденификатор есть
		if (!$this->profile) {
			$this->logout();
			return false;
		}
		return $this->profile;
	}
	
	/**
	 * Аутентификация пользователя
	 * @param unknown $user
	 */
	function login($identity, array $authData = []) {
		\Session::setValue('identity', $identity);
		$this->authData($authData);
	}
	
	
	function logout() {
		\Session::unsetValue('identity');
		$this->authData([]);
	}
	
	/**
	 * Обёртка на установку параметров
	 * @param array $data
	 */
	function authData(array $data = null) {
		if (\Session::getValue('authData') === null) {
			\Session::setValue('authData', []);
		}

		$authData = \Session::getValue('authData');

		if (null !== $data) {
			foreach ($data as $k=>$v) {
				$authData[$k] = $v;
			}
			\Session::setValue('authData', $authData);
		}
		return $authData;
	}
	
	/**
	 * Нормализуем массив привилегий
	 * Приводим к массиву где ключ - ключ привилегии, а занчение 
	 * состоит из имени привилегии и массива значений требование (assert)
	 * @param unknown $privileges
	 */
	static function normalizePrivileges($privileges) {
		$privileges = (array)$privileges;
		$result = [];
		foreach ($privileges as $privilege=>$asserts) {
			if (is_int($privilege)) {
				$privilege = $asserts;
				$asserts = [];
			}
			$res = ['privilege'=>$privilege, 'asserts'=>$asserts];
			$key = md5(serialize($res));
			$result[$key] = $res;
		}		
		return $result;
	}

}